//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System.Collections.Generic;

using UnityEngine;

using Flare.Display;

namespace Flare.Script
{
    /// <summary>
    /// Simple class to hold and interpert SWF version 3 style AS2 scripts.
    /// </summary>
    public class AS2
    {
        internal List<Action> scriptActions { get; private set; }

        /// <summary>
        /// Initializes a new instance of the AS2 class.
        /// </summary>
        public AS2()
        {
            scriptActions = new List<Action>();
        }

        internal void AddAction(Action action)
        {
            scriptActions.Add(action);
        }

        public string[] GetScriptContents()
        {
            string[] list = new string[scriptActions.Count];
            int count = 0;

            foreach (Action action in scriptActions)
            {
                list[count] = action.ToString();
                count++;
            }
            return list;
        }

        internal void Execute(DisplayObject caller, Stage stage)
        {
            try
            {
                Environment env = new Environment(caller, stage);
                foreach (var action in scriptActions)
                {
                    action.Execute(env);
                }
            }
            catch(global::System.Exception e)
            {
                Log.Warning(Subsystem.Playback, e.Message);
            }
        }

        //--------------------------------------------------------------------------------

        internal class Environment
        {
            public DisplayObject caller { set; get; }
            public DisplayObject target { set; get; }
            public Stage stage { set; get; }

            public Environment(DisplayObject caller, Stage stage)
            {
                this.caller = caller;
                this.target = caller;
                this.stage = stage;
            }
        }

        //--------------------------------------------------------------------------------

        internal abstract class Action
        {
            public Action()
            {
            }

            public virtual void Execute(Environment env)
            {
            }
        }

        internal class GotoFrameAction : Action
        {
            public int frame { get; private set; }

            public GotoFrameAction(int frame)
            {
                this.frame = frame;
            }

            public override void Execute(Environment env)
            {
                if (env.target is MovieClip)
                {
                    (env.target as MovieClip).GotoAndStop(this.frame + 1);
                }
                else
                {
                    throw new global::System.ApplicationException("GotoFrame target " +
                        " not a MovieClip");
                }
            }

            public override string ToString ()
            {
                 return string.Format("GotoFrame frame:{0}", frame);
            }
        }

        internal class GetURLAction : Action
        {
            public string url { get; private set; }

            public string target { get; private set; }

            public GetURLAction(string url, string target)
            {
                this.url = url;
                this.target = target;
            }

            public override void Execute(Environment env)
            {
                if (this.url.StartsWith("FSCommand:"))
                {
                    NativeWindow.windowActiveForScripts.fsCommandCallback(
                        this.url.Substring(10), this.target);
                }
                else
                {
                    throw new global::System.ApplicationException("General GetURL not supported");
                }
            }

            public override string ToString ()
            {
                 return string.Format("GetURL url:{0} target:{1}", url, target);
            }
        }

        internal class NextFrameAction : Action
        {
            public NextFrameAction()
            {
            }

            public override void Execute(Environment env)
            {
                if (env.target is MovieClip)
                {
                    (env.target as MovieClip).NextFrame();
                }
                else
                {
                    throw new global::System.ApplicationException("NextFrame target " +
                        " not a MovieClip");
                }
            }

            public override string ToString ()
            {
                 return "NextFrame";
            }
        }

        internal class PreviousFrameAction : Action
        {
            public PreviousFrameAction()
            {
            }

            public override void Execute(Environment env)
            {
                if (env.target is MovieClip)
                {
                    (env.target as MovieClip).PrevFrame();
                }
                else
                {
                    throw new global::System.ApplicationException("PreviousFrame target " +
                        " not a MovieClip");
                }
            }

            public override string ToString ()
            {
                 return "PreviousFrame";
            }
        }

        internal class PlayAction : Action
        {
            public PlayAction()
            {
            }

            public override void Execute(Environment env)
            {
                if (env.target is MovieClip)
                {
                    (env.target as MovieClip).Play();
                }
                else
                {
                    throw new global::System.ApplicationException("Play target " +
                        " not a MovieClip");
                }
            }

            public override string ToString ()
            {
                 return "Play";
            }
        }

        internal class StopAction : Action
        {
            public StopAction()
            {
            }

            public override void Execute(Environment env)
            {
                if (env.target is MovieClip)
                {
                    (env.target as MovieClip).Stop();
                }
                else
                {
                    throw new global::System.ApplicationException("Stop target " +
                        " not a MovieClip");
                }
            }

            public override string ToString ()
            {
                 return "Stop";
            }
        }

        internal class SetTargetAction : Action
        {
            public string target { get; private set; }
            private string[] path { get; set; }

            public SetTargetAction(string target)
            {
                // TODO bwmott 2013-08-12: Verify target path format
                this.target = target;
                if (this.target.Equals(""))
                {
                    this.path = null;
                }
                else if (this.target.Contains("."))
                {
                    this.path = this.target.Split('.');
                }
                else
                {
                    this.path = new string[] { this.target };
                }
            }

            public override void Execute(Environment env)
            {
                if (this.path == null)
                {
                    env.target = env.caller;
                }
                else
                {
                    foreach (var element in path)
                    {
                        if (element.Equals("_parent"))
                        {
                            env.target = env.target.parent;
                        }
                        else if (element.Equals("_root"))
                        {
                            while ((env.target != null) && (env.target.parent != env.stage))
                            {
                                env.target = env.target.parent;
                            }

                            if (env.target == null)
                            {
                                throw new global::System.ApplicationException(
                                    string.Format("AS2 SetTarget not found: {0}", this.target));
                            }
                        }
                        else
                        {
                            if (env.target is DisplayObjectContainer)
                            {
                                env.target = (env.target as DisplayObjectContainer).
                                    GetChildByName(element);
                            }
                            else
                            {
                                env.target = null;
                            }

                            if (env.target == null)
                            {
                                throw new global::System.ApplicationException(
                                    string.Format("AS2 SetTarget not found: {0}", this.target));
                            }
                        }
                    }
                }
            }

            public override string ToString()
            {
                 return string.Format("SetTarget target:{0}", this.target);
            }
        }

        internal class GotoLabelAction : Action
        {
            public string label { get; private set; }

            public GotoLabelAction(string label)
            {
                this.label = label;
            }

            public override void Execute(Environment env)
            {
                if (env.target is MovieClip)
                {
                    (env.target as MovieClip).GotoAndStop(this.label);
                }
                else
                {
                    throw new global::System.ApplicationException("GotoLabel target " +
                        " not a MovieClip");
                }
            }

            public override string ToString ()
            {
                 return string.Format("GotoLabel label:{0}", label);
            }
        }
    }
}
